/*
 * Copyright (c) Zhongkehonglue Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Implementation of functions related to the DMC DEV module.
 * Author: wangchunyang
 * Create: 2025-05-23
 */

#include "dmcAvt.h"

#include <unordered_map>

#include "dmcLog.h"
#include "dmcSendAction.h"
#include "dmcConstant.h"

#ifdef LOG_TAG
#undef LOG_TAG
#endif
namespace DlnaDmc {

std::unordered_map<std::string, DmcTransportState> stateMap = {
    {"STOPPED", DMC_TRANSPORT_STOPPED},
    {"PLAYING", DMC_TRANSPORT_PLAYING},
    {"PAUSED_PLAYBACK", DMC_TRANSPORT_PAUSED_PLAYBACK},
    {"PAUSED_RECODING", DMC_TRANSPORT_PAUSED_RECODING},
    {"NO_MEDIA_PRESENT", DMC_TRANSPORT_NO_MEDIA_PRESENT},
    {"TRANSITIONING", DMC_TRANSPORT_TRANSITIONING}
};

dmc_int32 DmcSetAvTransportURI(dmc_uint32 deviceId, dmc_uint32 instanceId,
    const std::string &uri, const std::string &metaData)
{
    LOGI("set uri deviceId = %{public}d, instanceId = %{public}d, \
        uri = %{public}s", deviceId, instanceId, uri.c_str());
    if(uri.empty()){
        LOGE("dmc set avt transport url failed, url is empty");
        return DMC_INVALID_PARAMETER;
    }
    if(metaData.empty()) {
        LOGW("dmc set avt transport url, meta data is empty");
    }
    DmcActionInfo actionInfo;
    actionInfo.actionName = "SetAVTransportURI";
    actionInfo.serviceType = avtTransportServiceTypeStr;
    actionInfo.paraInfo.insert({"InstanceID", std::to_string(instanceId)});
    actionInfo.paraInfo.insert({"CurrentURI", uri});
    actionInfo.paraInfo.insert({"CurrentURIMetaData", metaData});

    IXML_Document *resultDoc = nullptr;
    dmc_int32 retVal = DmcSendAction(deviceId, actionInfo, &resultDoc);
    if (retVal != DMC_SUCCESS) {
        LOGE("DmcSendAction failed, error code = %{public}d", retVal);
        return DMC_FAILURE;
    }
    ixmlDocument_free(resultDoc);
    return DMC_SUCCESS;
}

dmc_int32 DmcPlay(dmc_uint32 deviceId, dmc_uint32 instanceId, const std::string &playSpeed)
{
    LOGI("paly deviceId = %{public}d, instanceId = %{public}d,\
        playSpeed = %{public}s", deviceId, instanceId, playSpeed.c_str());
    DmcActionInfo actionInfo;
    actionInfo.actionName = "Play";
    actionInfo.serviceType = avtTransportServiceTypeStr;
    actionInfo.paraInfo.insert({"InstanceID", std::to_string(instanceId)});
    actionInfo.paraInfo.insert({"Speed", playSpeed});

    IXML_Document *resultDoc = nullptr;
    dmc_int32 retVal = DmcSendAction(deviceId, actionInfo, &resultDoc);
    if (retVal != DMC_SUCCESS) {
        LOGE("DmcSendAction failed, error code = %d", retVal);
        return DMC_FAILURE;
    }
    ixmlDocument_free(resultDoc);
    return DMC_SUCCESS;
}

dmc_int32 DmcPause(dmc_uint32 deviceId, dmc_uint32 instanceId)
{
    LOGI("pause deviceId = %{public}d, instanceId = %{public}d,",deviceId, instanceId);
    DmcActionInfo actionInfo;
    actionInfo.actionName = "Pause";
    actionInfo.serviceType = avtTransportServiceTypeStr;
    actionInfo.paraInfo.insert({"InstanceID", std::to_string(instanceId)});

    IXML_Document *resultDoc = nullptr;
    dmc_int32 retVal = DmcSendAction(deviceId, actionInfo, &resultDoc);
    if (retVal != DMC_SUCCESS) {
        LOGE("DmcSendAction failed, error code = %d", retVal);
        return DMC_FAILURE;
    }
    ixmlDocument_free(resultDoc);
    return DMC_SUCCESS;
}

dmc_int32 DmcStop(dmc_uint32 deviceId, dmc_uint32 instanceId)
{
    LOGI("stop deviceId = %{public}d, instanceId = %{public}d,", deviceId, instanceId);
    DmcActionInfo actionInfo;
    actionInfo.actionName = "Stop";
    actionInfo.serviceType = avtTransportServiceTypeStr;
    actionInfo.paraInfo.insert({"InstanceID", std::to_string(instanceId)});

    IXML_Document *resultDoc = nullptr;
    dmc_int32 retVal = DmcSendAction(deviceId, actionInfo, &resultDoc);
    if (retVal != DMC_SUCCESS) {
        LOGE("DmcSendAction failed, error code = %d", retVal);
        return DMC_FAILURE;
    }
    ixmlDocument_free(resultDoc);
    return DMC_SUCCESS;
}

std::string DmcGetSeekUnit(DmcSeekMode seekMode)
{
    std::string seekUnit;
    switch (seekMode) {
        case DMC_SEEK_MODE_TRACK_NR:
            seekUnit = "TRACK_NR";
            break;

        case DMC_SEEK_MODE_ABS_TIME:
            seekUnit = "ABS_TIME";
            break;

        case DMC_SEEK_MODE_REL_TIME:
            seekUnit = "REL_TIME";
            break;

        case DMC_SEEK_MODE_ABS_COUNT:
            seekUnit = "ABS_COUNT";
            break;

        case DMC_SEEK_MODE_REL_COUNT:
            seekUnit = "REL_COUNT";
            break;

        case DMC_SEEK_MODE_CHANNEL_FREQ:
            seekUnit = "CHANNEL_FREQ";
            break;

        case DMC_SEEK_MODE_TAPE_INDX:
            seekUnit = "TAPE-INDEX";
            break;

        case DMC_SEEK_MODE_FRAME:
            seekUnit = "FRAME";
            break;

        default:
            LOGE("Invalid Seek Unit value = %{public}d", seekMode);
    }
    return seekUnit;
}

dmc_int32 DmcSeek(dmc_uint32 deviceId, dmc_uint32 instanceId, DmcSeekMode seekMode,
    const std::string &seekTarget)
{
    LOGI("stop deviceId = %{public}d, instanceId = %{public}d,",deviceId, instanceId);
    DmcActionInfo actionInfo;
    actionInfo.actionName = "Seek";
    actionInfo.serviceType = avtTransportServiceTypeStr;
    actionInfo.paraInfo.insert({"InstanceID", std::to_string(instanceId)});

    std::string seekUnit = DmcGetSeekUnit(seekMode);
    if (seekUnit.empty()) {
        LOGE("seek unit is empty.");
        return DMC_FAILURE;
    }
    actionInfo.paraInfo.insert({"Unit", seekUnit});
    actionInfo.paraInfo.insert({"Target", seekTarget});

    IXML_Document *resultDoc = nullptr;
    dmc_int32 retVal = DmcSendAction(deviceId, actionInfo, &resultDoc);
    if (retVal != DMC_SUCCESS) {
        LOGE("DmcSendAction failed, error code = %d", retVal);
        return DMC_FAILURE;
    }
    ixmlDocument_free(resultDoc);
    return DMC_SUCCESS;
}

bool IsWhiteSpace(unsigned char ch)
{
    return ((' ' == (ch)) || ('\n' == (ch)) || ('\r' == (ch)) || ('\t' == (ch)));
}

dmc_int32 DmcConvAscToUInt(unsigned char *asc, dmc_uint32 &val)
{
    val = 0;
    unsigned char *bufPtr = asc;
    while (IsWhiteSpace(*bufPtr)) {
        bufPtr++;
    }
    if (*bufPtr == '\0') {
        LOGE("Invalid Data received in string: %{public}s", asc);
        return DMC_FAILURE;
    }

    while (*bufPtr != '\0') {
        unsigned char tempChar = *bufPtr;
        if ((tempChar < '0') || (tempChar > '9')) {
            LOGE("Invalid Data received in string: %{public}s", asc);
            return DMC_FAILURE;
        }
        dmc_uint32 temp = val * DMC_DECIMAL_BASE;
        val = temp + (tempChar - '0');
        if (val < temp) {
            LOGE("Invalid Data received in string: %{public}s", asc);
            return DMC_FAILURE;
        }
        bufPtr++;
    }
    return DMC_SUCCESS;
}

dmc_int32 DmcGetTrackInfo(IXML_Document *resultDoc, DmrTrackPosition &trackPos)
{
    if (resultDoc == nullptr) {
        LOGE("resultDoc nullptr");
        return DMC_FAILURE;
    }
    const char *resultVal = DmcGetXmlElementValueFromDoc(resultDoc, "Track");
    if (resultVal == nullptr) {
        LOGE("get track failed.");
       return DMC_FAILURE;
    }
    dmc_uint32 currentTrack;
    auto ret = DmcConvAscToUInt((unsigned char *)resultVal, currentTrack);
    if (ret != DMC_SUCCESS) {
        return ret;
    }
    trackPos.currentTrack = currentTrack;

    // some devices return null.
    resultVal = DmcGetXmlElementValueFromDoc(resultDoc, "TrackDuration");
    trackPos.trackDur = resultVal;

    resultVal = DmcGetXmlElementValueFromDoc(resultDoc, "TrackMetaData");
    if (resultVal == nullptr) {
        LOGE("get TrackMetaData failed.");
        return DMC_FAILURE;
    }
    trackPos.trackMetaData = resultVal;

    resultVal = DmcGetXmlElementValueFromDoc(resultDoc, "TrackURI");
    if (resultVal == nullptr) {
        LOGE("get TrackURI failed.");
        return DMC_FAILURE;
    }
    trackPos.trackUri = resultVal;
    return DMC_SUCCESS;
}

dmc_int32 DmcGetTimeAndCountInfo(IXML_Document *resultDoc, DmrTrackPosition &trackPos)
{
    if (resultDoc == nullptr) {
        LOGE("resultDoc nullptr.");
        return DMC_FAILURE;
    }
    const char *resultVal = DmcGetXmlElementValueFromDoc(resultDoc, "RelTime");
    if (resultVal == nullptr) {
        LOGE("get RelTimef iled.");
        return DMC_FAILURE;
    }
    trackPos.relTime = resultVal;

    resultVal = DmcGetXmlElementValueFromDoc(resultDoc, "AbsTime");
    if (resultVal == nullptr) {
        LOGE("get failed.");
        return DMC_FAILURE;
    }
    trackPos.absTime = resultVal;

    resultVal = DmcGetXmlElementValueFromDoc(resultDoc, "RelCount");
    if (resultVal == nullptr) {
        LOGE("get RelCount failed.");
        return DMC_FAILURE;
    }

    dmc_uint32 relCount;
    auto ret = DmcConvAscToUInt((unsigned char *)resultVal, relCount);
    if (ret != DMC_SUCCESS) {
        return ret;
    }
    trackPos.relCount = relCount;

    resultVal = DmcGetXmlElementValueFromDoc(resultDoc, "AbsCount");
    if (resultVal == nullptr) {
        LOGE("get AbsCount failed.");
        return DMC_FAILURE;
    }
    dmc_uint32 absCount;
    ret = DmcConvAscToUInt((unsigned char *)resultVal, absCount);
    if (ret != DMC_SUCCESS) {
        return ret;
    }
    trackPos.absCount = absCount;
    return DMC_SUCCESS;
}

dmc_int32 DmcGetPositionResult(IXML_Document *resultDoc, DmrTrackPosition &trackPos)
{
    if (resultDoc == nullptr) {
        LOGE("resultDoc nullptr.");
        return DMC_FAILURE;
    }
    auto ret = DmcGetTrackInfo(resultDoc, trackPos);
    if (ret != DMC_SUCCESS) {
        return ret;
    }

    ret = DmcGetTimeAndCountInfo(resultDoc, trackPos);
    if (ret != DMC_SUCCESS) {
        return ret;
    }
    return DMC_SUCCESS;
}

dmc_int32 DmcGetPositionInfo(dmc_uint32 deviceId, dmc_uint32 instanceId, DmrTrackPosition &trackPos)
{
    LOGI("get position info, deviceId = %{public}d, instanceId = %{public}d,",deviceId, instanceId);
    DmcActionInfo actionInfo;
    actionInfo.actionName = "GetPositionInfo";
    actionInfo.serviceType = avtTransportServiceTypeStr;
    actionInfo.paraInfo.insert({"InstanceID", std::to_string(instanceId)});

    IXML_Document *resultDoc = nullptr;
    dmc_int32 retVal = DmcSendAction(deviceId, actionInfo, &resultDoc);
    if (retVal != DMC_SUCCESS) {
        LOGE("DmcSendAction failed, error code = %d", retVal);
        return DMC_FAILURE;
    }

    retVal  = DmcGetPositionResult(resultDoc, trackPos);
    ixmlDocument_free(resultDoc);
    if (retVal != DMC_SUCCESS) {
        return DMC_FAILURE;
    }
    return DMC_SUCCESS;
}

dmc_int32 DmcGetTransportInfoFromDoc(IXML_Document *resultDoc, DmcTransportState &transportState,
    DmcTransportStatus &transportStatus, std::string &playSpeed)
{
    if (resultDoc == nullptr) {
        LOGE("resultDoc nullptr");
        return DMC_FAILURE;
    }
    const char *state = DmcGetXmlElementValueFromDoc(resultDoc, "CurrentTransportState");
    if (state == nullptr) {
        LOGE("get state failed.");
       return DMC_FAILURE;
    }
    LOGI("CurrentTransportState, %{public}s", state);
    if (stateMap.find(state) == stateMap.end()) {
        LOGE("can not find state, %{public}s", state);
        return DMC_FAILURE;
    }
    transportState = stateMap[state]; 

    const char *status = DmcGetXmlElementValueFromDoc(resultDoc, "CurrentTransportStatus");
    if (status == nullptr) {
        LOGE("get status failed.");
       return DMC_FAILURE;
    }
    LOGI("CurrentTransportStatus, %{public}s", status);
    if (status == std::string("OK")) {
        transportStatus = DMC_TRANSPORT_OK;
    } else if (status == std::string("ERROR_OCCURRED")){
        transportStatus = DMC_TRANSPORT_ERROR;
    } else {
        LOGE("can not find status, %{public}s", status);
       return DMC_FAILURE;
    }

    const char *speed = DmcGetXmlElementValueFromDoc(resultDoc, "CurrentSpeed");
    if (speed == nullptr) {
        LOGE("get speed failed.");
       return DMC_FAILURE;
    }
    LOGI("CurrentSpeed, %{public}s", speed);
    playSpeed = speed;
    return DMC_SUCCESS;
}

dmc_int32 DmcGetTransportInfo(dmc_uint32 deviceId, dmc_uint32 instanceId,
    DmcTransportState &transportState, DmcTransportStatus &transportStatus, std::string &playSpeed)
{
    LOGI("get transport info, deviceId = %{public}d, instanceId = %{public}d,",deviceId, instanceId);
    DmcActionInfo actionInfo;
    actionInfo.actionName = "GetTransportInfo";
    actionInfo.serviceType = avtTransportServiceTypeStr;
    actionInfo.paraInfo.insert({"InstanceID", std::to_string(instanceId)});

    IXML_Document *resultDoc = nullptr;
    dmc_int32 retVal = DmcSendAction(deviceId, actionInfo, &resultDoc);
    if (retVal != DMC_SUCCESS) {
        LOGE("DmcSendAction failed, error code = %d", retVal);
        return DMC_FAILURE;
    }

    retVal  = DmcGetTransportInfoFromDoc(resultDoc, transportState, transportStatus, playSpeed);
    ixmlDocument_free(resultDoc);
    if (retVal != DMC_SUCCESS) {
        return DMC_FAILURE;
    }
    return DMC_SUCCESS;
}
}


